//
// Created by liaohailong on 2025/4/18.
//

#include "HelloPlaylistHandler.hpp"

HelloPlaylistHandler::HelloPlaylistHandler(const std::shared_ptr<HelloCacheManager> &cache)
        : HelloRequestHandler("HelloPlaylistHandlerVictor"), cache(cache), ht(nullptr),
          handler(nullptr),
          transformer(nullptr), userdata(nullptr)
{
    // 创建就启动 handler
    ht = new NHandlerThread("hello-playlist-ht");
    ht->startSync();
    handler = ht->getHandler();
    logger.i("HelloPlaylistHandler::HelloPlaylistHandler(%p)", this);
}

HelloPlaylistHandler::~HelloPlaylistHandler()
{
    // 释放 handler 资源
    delete ht;
    ht = nullptr;
    handler = nullptr;

    logger.i("HelloPlaylistHandler::~HelloPlaylistHandler(%p)", this);
}

void HelloPlaylistHandler::setUrlTransformer(UrlTransformer _transformer, void *_userdata)
{
    this->transformer = _transformer;
    this->userdata = _userdata;
}

std::shared_ptr<HelloCacheManager> HelloPlaylistHandler::getCacheManager() const
{
    return cache;
}

void HelloPlaylistHandler::acceptClient(std::shared_ptr<HelloHttpRequest> request)
{
    // 处理 m3u8 文件，
    // 是否本地有缓存？
    // 替换m3u8中所有url
    const std::string &url = request->getRealUrl();
    std::shared_ptr<HelloRange> range = request->getRange();
    const int fd = request->getConnFd();
    const std::shared_ptr<HelloCacheInfo> &cacheInfo = cache->getCacheInfo(url);
    const std::string cacheDir = cacheInfo->cacheDir->getAbsolutFilepath();
    logger.i("client[%d] accepted cacheDir: %s", fd, cacheDir.c_str());

    std::shared_ptr<HelloFile> file;
    if (cacheInfo->available())
    {
        // 尝试从缓存中查找
        const std::string &filename = cacheInfo->getFilename();
        file = cacheInfo->getCacheFile(filename); // m3u8文件
    }

    if (file && file->isFile())
    {
        // 确定命中缓存
        auto *ctx = new HelloPlaylistCtx();
        ctx->handler = this;
        ctx->request = request;
        ctx->cacheInfo = cacheInfo;
        ctx->file = file;
        handler->post(HelloPlaylistHandler::write2client, ctx);
        return;
    }

    // 需要请求网络
    request->setWrite2Cache(true); // 把m3u8文件内容写到本地
    auto *ctx = new HelloPlaylistCtx();
    ctx->handler = this;
    ctx->request = request;
    ctx->cacheInfo = cacheInfo;
    ctx->file = nullptr;
    handler->post(HelloPlaylistHandler::requestNetwork, ctx);
}

std::string HelloPlaylistHandler::parseM3u8(const std::shared_ptr<HelloFile> &file)
{
    std::string content = file->readFileAsString();
    // 将内容替换：所有请求url，都替换成我们的代理url
    std::string prefix = "http://192.168.1.98:" + std::to_string(8898) + "/hellovideocache?url=";
    content = HelloPlaylistHandler::transformM3u8Content(content, prefix);

    return content;
}

void HelloPlaylistHandler::write2client(void *userdata)
{
    auto native = reinterpret_cast<HelloPlaylistCtx *>(userdata);
    std::shared_ptr<HelloPlaylistCtx> ptr(native); // 智能指针管理内存释放

    HelloPlaylistHandler *handler = native->handler;
    Logger &logger = handler->logger;
    const int fd = native->request->getConnFd();
    std::shared_ptr<HelloRange> range = native->request->getRange();
    std::shared_ptr<HelloCacheInfo> cacheInfo = native->cacheInfo;
    cacheInfo->update(); // 重新加载一下index文件中的内容
    std::shared_ptr<HelloFile> file = native->file;

    // 发送 headers 信息给客户端
    int64_t contentLength = file->getLength(); // 因为改过内容，所以文件大小以实际文件大小为准
    std::string type = cacheInfo->getContentType();
    std::string headers = HelloHeader::getResponseHeader(range, contentLength, type);
    size_t n = headers.size();
    ssize_t writen = send(fd, headers.c_str(), n, 0);
    logger.i("[CACHE]Response to client[%d] writen[%d]: \n%s", fd, writen,
             headers.c_str());
    if (writen != n)
    {
        logger.e("client[%d] send headers error writen[%d] != n[%d]", fd, writen, n);
        return;
    }

    // 发送 m3u8 文件内容给客户端
    std::string content = handler->parseM3u8(file);
    n = content.size();
    logger.i("client[%d] content size[%d]", fd, n);
    writen = send(fd, content.c_str(), n, 0);
    if (writen != n)
    {
        logger.e("client[%d] send content error writen[%d] != n[%d]", fd, writen, n);
        return;
    }

    logger.i("client[%d] write m3u8 complete", fd);
}

void HelloPlaylistHandler::requestNetwork(void *userdata)
{
    auto native = reinterpret_cast<HelloPlaylistCtx *>(userdata);
    HelloPlaylistHandler *handler = native->handler;
    Logger &logger = native->handler->logger;
    std::shared_ptr<HelloCacheManager> cache = native->handler->getCacheManager();
    std::shared_ptr<HelloHttpRequest> request = native->request;
    const int fd = request->getConnFd();
    const std::string &url = request->getRealUrl();
    std::shared_ptr<HelloRange> range = request->getRange();
    std::shared_ptr<HelloCacheInfo> cacheInfo = native->cacheInfo;

    // 请求网络下载 m3u8 文件
    HelloDownloader downloader;
    std::shared_ptr<HelloDownloader::HelloDownloadCtx> params
            = downloader.newParams(cache, request, range);
    params->sendHeader = false; // 请求网络的时候不写headers给客户端，后续要改m3u8内容
    bool write2Cache = request->isWrite2Cache(); // 是否需要写入本地缓存
    // 是否需要写入本地缓存
    if (write2Cache && !cacheInfo->ensure())
    {
        // 一般不会出现这种情况
        const std::string &cacheDir = cacheInfo->cacheDir->getAbsolutFilepath();
        logger.e("cannot ensure cache dir [%s]", cacheDir.c_str());
        return;
    }
    if (write2Cache)
    {
        const std::string &filename = cacheInfo->getFilename(); // "xxx.m3u8"
        std::shared_ptr<HelloFile> file = cacheInfo->getCacheFile(filename);
        const std::string &filepath = file->getAbsolutFilepath();
        auto ofs = std::make_shared<std::ofstream>(filepath, std::ios::binary);
        if (!ofs->is_open())
        {
            logger.e("cannot open file[%s]", filename.c_str());
            return;
        }
        params->ofs = ofs;
        logger.i("client[%d] prepare write to local cache[%s]", fd, filepath.c_str());
    }

    bool complete = downloader.download(params);
    std::string ret(complete ? "true" : "false");
    logger.i("HelloPlaylistHandler client[%d] complete[%s]", fd, ret.c_str());
    if (complete)
    {
        // 必须调用，ofs是懒写入，数据最后还剩一点，需要flush
        if (params->ofs)
        {
            params->ofs->flush();
            params->ofs->close();
            params->ofs = nullptr;
        }

        // 从缓存中查找
        const std::string &filename = cacheInfo->getFilename(); // "xxx.m3u8"
        std::shared_ptr<HelloFile> file = cacheInfo->getCacheFile(filename); // m3u8文件

        // 需要把 m3u8 中的相对路径全部替换成 完整url
        std::string prefix = url.substr(0, url.find_last_of('/') + 1);
        std::string filepath = file->getAbsolutFilepath();
        logger.i("client[%d] m3u8 filepath: %s", fd, filepath.c_str());
        std::string content = file->readFileAsString(); // m3u8内容
        logger.i("client[%d] m3u8 1 content[%d] line:", fd, content.size());
        content = HelloPlaylistHandler::transformM3u8Content(content, prefix);
        logger.i("client[%d] m3u8 2 content[%d] line:", fd, content.size());

        bool success = file->writeString2File(content);
        if (!success)
        {
            logger.e("client[%d] cannot write m3u8 content into file", fd);
            return;
        }

        // 确定命中缓存
        auto *ctx = new HelloPlaylistCtx();
        ctx->handler = native->handler;
        ctx->request = request;
        ctx->cacheInfo = cacheInfo;
        ctx->file = file;
        handler->handler->post(HelloPlaylistHandler::write2client, ctx);
    }

}

/**
 * 转换m3u8文件中的内容，把相对资源地址都换成完整的url
 * @param content m3u8整体内容
 * @param prefix 拼接在相对资源地址前面的内容
 * @return 转换好的内容
 */
std::string HelloPlaylistHandler::transformM3u8Content(const std::string &content,
                                                       const std::string &prefix)
{
    std::istringstream iss(content);
    std::ostringstream oss;
    std::string line;

    while (std::getline(iss, line))
    {
        // 默认保留原始行
        std::string output;

        if (line.empty())
        {
            // 空行，直接输出
            output = line;
        } else if (line[0] == '#')
        {
            // 直接输出
            output = line;
        } else
        {
            output = prefix + line;
        }

        // 恢复换行符（使用统一的 \n）
        output += "\n";

        oss << output;
    }

    return oss.str();
}